﻿using System;
using System.Collections.Generic;
using Ooaavee.Data.Model;

namespace Ooaavee.Data.Queries
{
    /// <summary>
    /// Represents the main entry point for the query framework.
    /// </summary>
    public sealed class QueryContext
    {
        /// <summary>
        /// Occurs when a cancel query request has been invoked.
        /// </summary>
        internal event EventHandler CancelQueryRequested;

        /// <summary>
        /// Occurs when a query has been cancelled.
        /// </summary>
        public event EventHandler QueryCancelled;

        /// <summary>
        /// Occurs when a query raises an error.
        /// </summary>
        public event EventHandler<QueryErrorEventArgs> QueryError;

        /// <summary>
        /// Occurs when a SQL statement has been executed.
        /// </summary>
        public event EventHandler<SqlListenerEventArgs> SqlListener;

        /// <summary>
        /// Occurs when a query has been started.
        /// </summary>
        public event EventHandler QueryStarted;

        /// <summary>
        /// Occurs when a query has been completed.
        /// </summary>
        public event EventHandler QueryCompleted;

        /// <summary>
        /// No public constructor available, use CreateQueryContext methods.
        /// </summary>
        private QueryContext() { }

        /// <summary>
        /// Creates a new query context.
        /// </summary>
        /// <param name="source">Defines the source of a relational model.</param>
        /// <returns>The query context.</returns>
        public static QueryContext CreateQueryContext(RelationalModelSource source)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }

            return CreateQueryContextInternal(source, RelationalModelLoader.Load(source));
        }

        /// <summary>
        /// Creates a new query context.
        /// </summary>
        /// <param name="source">Defines the source of a relational model.</param>
        /// <param name="relationalModel">A relational model of data.</param>
        /// <returns>The query context.</returns>
        public static QueryContext CreateQueryContext(RelationalModelSource source, RelationalModel relationalModel)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }

            if (relationalModel == null)
            {
                throw new ArgumentNullException("relationalModel");
            }

            return CreateQueryContextInternal(source, relationalModel);
        }

        private static QueryContext CreateQueryContextInternal(RelationalModelSource source, RelationalModel relationalModel)
        {
            Connections connections = new Connections(source);

            QueryContext context = new QueryContext { Connections = connections, RelationModel = relationalModel };

            // Map a context to a relational model
            lock (Contexts)
            {
                Contexts[relationalModel] = context;
            }            

            return context;
        }

        private static readonly Dictionary<RelationalModel, QueryContext> Contexts = new Dictionary<RelationalModel, QueryContext>();

        internal static QueryContext GetMappedContext(Table table)
        {
            lock (Contexts)
            {
                RelationalModel model = table.Parent;
                if (model == null)
                {
                    throw new InvalidOperationException("");
                }

                if (Contexts.ContainsKey(model) == false)
                {
                    throw new InvalidOperationException("");                    
                }
                return Contexts[model];
            }
        }

        /// <summary>
        /// Tries to cancel the execution of a query.
        /// </summary>
        public void Cancel()
        {
            OnCancelQueryRequested(EventArgs.Empty);
        }

        /// <summary>
        /// Gets and sets the database connection provider.
        /// </summary>
        internal Connections Connections { private set; get; }

        /// <summary>
        /// Gets and sets the relational model.
        /// </summary>
        internal RelationalModel RelationModel { private set; get; }

        /// <summary>
        /// Raises the <see cref="CancelQueryRequested"/> event.
        /// </summary>
        /// <param name="e">An <see cref="EventArgs"/> that contains the event data.</param>
        internal void OnCancelQueryRequested(EventArgs e)
        {
            var handler = CancelQueryRequested;
            if (handler != null)
            {
                handler(this, e);
            }
        }

        /// <summary>
        /// Raises the <see cref="QueryError"/> event.
        /// </summary>
        /// <param name="e">An <see cref="QueryErrorEventArgs"/> that contains the event data.</param>
        internal void OnQueryErrorEvent(QueryErrorEventArgs e)
        {
            var handler = QueryError;
            if (handler != null)
            {
                handler(this, e);
            }
        }

        /// <summary>
        /// Raises the <see cref="SqlListener"/> event.
        /// </summary>
        /// <param name="e">An <see cref="SqlListenerEventArgs"/> that contains the event data.</param>
        internal void OnSqlListenerEvent(SqlListenerEventArgs e)
        {
            var handler = SqlListener;
            if (handler != null)
            {
                handler(this, e);
            }
        }

        /// <summary>
        /// Raises the <see cref="QueryStarted"/> event.
        /// </summary>
        /// <param name="e">An <see cref="EventArgs"/> that contains the event data.</param>
        internal void OnQueryStarted(EventArgs e)
        {
            var handler = QueryStarted;
            if (handler != null)
            {
                handler(this, e);
            }
        }

        /// <summary>
        /// Raises the <see cref="QueryCompleted"/> event.
        /// </summary>
        /// <param name="e">An <see cref="EventArgs"/> that contains the event data.</param>
        internal void OnQueryCompleted(EventArgs e)
        {
            var handler = QueryCompleted;
            if (handler != null)
            {
                handler(this, e);
            }
        }

        /// <summary>
        /// Raises the <see cref="QueryCancelled"/> event.
        /// </summary>
        /// <param name="e">An <see cref="EventArgs"/> that contains the event data.</param>
        internal void OnQueryCancelled(EventArgs e)
        {
            var handler = QueryCancelled;
            if (handler != null)
            {
                handler(this, e);
            }            
        }

        public QueryResult Select(Table table)
        {
            if (table == null)
            {
                throw new ArgumentNullException("table");
            }

            return table.Select();
        }

        public QueryResult Select(Table table, string customClause)
        {
            if (table == null)
            {
                throw new ArgumentNullException("table");
            }

            if (customClause == null)
            {
                throw new ArgumentNullException("customClause");
            }

            return table.Select(customClause);
        }
    }
}
